package org.raymond.iworks.study.basic.feature;

import org.junit.jupiter.api.Test;

import java.util.*;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/*
    很多人在设计接口的时候，会陷入两难的境地。我返回的数据，是直接返回Stream，还是返回List？
    如果你返回的是一个List，比如ArrayList，那么你去修改这个List，会直接影响里面的值，除非你使用不可变的方式对其进行包裹。同样的，数组也有这样的问题。
    但对于一个Stream来说，是不可变的，它不会影响原始的集合。对于这种场景，我们推荐直接返回Stream流，而不是返回集合。
这种方式还有一个好处，能够强烈的暗示API使用者，多多使用Stream相关的函数，以便能够统一代码风格

    不可变集合是一个强需求，它能防止外部的函数对这些集合进行不可预料的修改。
    在guava中，就有大量的Immutable类支持这种包裹。再举一个例子，Java的枚举，它的values()方法，为了防止外面的api对枚举进行修改，就只能拷贝一份数据。
    但是，如果你的api，面向的是最终的用户，不需要再做修改，那么直接返回List就是比较好的，比如函数在Controller中
 */
public class JDK8Test {

    /**
     * 1.不需要参数,返回值为 5
     * ()->5
     * 2. 接收一个参数(数字类型),返回其2倍的值
     * x ->2*x
     * 3. 接受2个参数(数字),并返回他们的差值
     * (x,y)->x –y
     * 4. 接收2个int型整数,返回他们的和
     * (int x, int y)->x +y
     * 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)
     * (String s)->System.out.print(s)
     */
    @Test
    public void lambdaTest01(){
        JDK8Test tester = new JDK8Test();
        // 类型声明
        MathOperation addition = (int a, int b) -> a + b;
        // 不用类型声明
        MathOperation subtraction = (a, b) -> a - b;
        // 大括号中的返回语句
        MathOperation multiplication = (int a, int b) -> { return a * b; };
        // 没有大括号及返回语句
        MathOperation division = (int a, int b) -> a / b;

        System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
        System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
        System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
        System.out.println("10 / 5 = " + tester.operate(10, 5, division));
        // 不用括号
        GreetingService greetService1 = message -> System.out.println("Hello " + message);
        // 用括号
        GreetingService greetService2 = (message) -> System.out.println("Hello " + message);

        greetService1.sayMessage("Runoob");
        greetService2.sayMessage("Google");
    }

    @Test
    public void collectorsTest01(){
        Student s1 = new Student("raymond", 35, "male", 100);
        Student s2 = new Student("alva", 18, "female", 85.5f);
        Student s3 = new Student("alice", 19, "female", 91.5f);
        Student s4 = new Student("ann", 18, "female", 90);

        List list = List.of(s1, s2, s3, s4);
        Map<Integer, Student> map = Stream.of(s1,s2,s3,s4).collect(Collectors.toMap(Student::age, Function.identity(),
                BinaryOperator.minBy(Comparator.comparing(Student::name))));
        System.out.println(map);

        String names = Stream.of(s1,s2,s3,s4).map(Student::name).collect(Collectors.joining("_"));
        System.out.println(names);

        long countAge = Stream.of(s1,s2,s3,s4).count();
        System.out.println(countAge);
        countAge = Stream.of(s1, s2, s3, s4).mapToInt(Student::age).sum();

        // 分组
        Map<Integer, List<Student>> groupingByMap = Stream.of(s1, s2, s3, s4).collect(Collectors.groupingBy(Student::age));
        System.out.println("groupingByMap: "+groupingByMap);

        Map<Integer, Set<String>> groupByMappingMap = Stream.of(s1,s2,s3,s4)
                .collect(Collectors.groupingBy(Student::age, Collectors.mapping(Student::name, Collectors.toSet())));
        System.out.println("groupByMappingMap:"+groupByMappingMap);

        // partitioningBy 分区, 与groupingBy不一样的是, partitioningBy的key只有true和false
        Map<Boolean, List<Student>> partitioningByMap = Stream.of(s1, s2, s3, s4).collect(Collectors.partitioningBy(e->e.age()!=18));
        System.out.println("partitioningByMap:"+partitioningByMap);

        // 构建深层map
        Map<Integer, Map<String, List<Student>>> studentByBy = Stream.of(s1, s2, s3, s4)
                .collect(Collectors.groupingBy(Student::age, Collectors.groupingBy(Student::sex)));
        System.out.println("studentByBy:"+studentByBy);
    }

    /*
    map()和flatMap()方法都可以应用于Stream<T>和Optional<T>对象。并且都返回Stream<R>或Optional <U>对象。
    区别在于map()操作为每个输入值生成一个输出值，而flatMap()操作为每个输入值生成任意数量（零个或多个）的输出值。
    在flatMap()中，每个输入始终是一个集合，可以是List或Set或Map。
    map()操作采用一个方法，该方法针对输入流中的每个值调用，并生成一个结果值，该结果值返回至stream。
    flatMap()操作采用的功能在概念上消耗一个集合对象并产生任意数量的值。但是在Java中方法返回任意数目的值很麻烦，因为方法只能返回void或一个对象。
     */
    @Test
    public void flatMapTest01(){
        Student s1 = new Student("raymond", 35, "male", 100);
        Student s2 = new Student("alva", 18, "female", 85.5f);
        Student s3 = new Student("alice", 19, "female", 91.5f);
        Student s4 = new Student("ann", 18, "female", 90);

        List<Student> list1 = List.of(s1,s2);
        List<Student> list2 = List.of(s3,s4);
        List<List<Student>> nestedList = List.of(list1, list2);
        list1.stream().map(Student::name).forEach(System.out::println);
        list2.stream().map(Student::name).forEach(System.out::println);
        System.out.println("---");
        // 嵌套集合中的元素是集合
        nestedList.stream().map(x->x.stream().map(e->e.name()).collect(Collectors.toList())).forEach(System.out::println);
        System.out.println("---");
        // 合并stream, 比如将嵌套一层,共两层的集合,合并为只有一层元素的集合
        nestedList.stream().flatMap(Collection::stream).map(Student::name).forEach(System.out::println);

        Map<Integer, Map<String, List<Student>>> studentByBy = Stream.of(s1, s2, s3, s4)
                .collect(Collectors.groupingBy(Student::age, Collectors.groupingBy(Student::sex)));
        // 从深层嵌套中提取元素集合
        List<Student> students = studentByBy.values().stream().flatMap(map->map.values().stream().flatMap(Collection::stream)).toList();
        System.out.println(students);
    }

    interface MathOperation {
        int operation(int a, int b);
    }
    interface GreetingService {
        void sayMessage(String message);
    }

    private int operate(int a, int b, MathOperation mathOperation){
        return mathOperation.operation(a, b);
    }


    // 给定单词表["java","scala"],想要返回列表["j","a","v","s","c","l"].
    public void wordsTest(){
        String[] words = {"java","scala"};

        // 传递给map方法的Lambda为每个单词返回了一个String[](String列表). 因此, map返回的流实际上是Stream<String[]>类型
        // 但真正想要的是用Stream<String>来表示一个字符流
        Arrays.stream(words).map(w->w.split("")).distinct().forEach(x-> System.out.println(x.toString()));
        // 首先,你需要一个字符流,而不是字符串数组流.有一个叫作Arrays.stream()的方法可以接受一个数组并产生一个流
        // .map(word->word.split("")) ----> 将每个单词转换为由其字母构成的数组
        // .map(Arrays::stream) ----------> 让每个数组变成一个单独的流
        Arrays.stream(words).map(w->w.split("")).map(Arrays::stream).distinct().forEach(x->System.out.println(x));
        Arrays.stream(words).map(w->w.split("")).map(Arrays::stream).distinct().forEach(x->x.forEach(System.out::print));
        System.out.println("---");
        // 使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容.所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流
        Arrays.stream(words).map(w->w.split("")).flatMap(Arrays::stream).distinct().forEach(System.out::print);
    }

    /*
    Stream类中有三种reduce，分别接受1个参数，2个参数，和3个参数，首先来看一个参数的情况：
    Optional<T> reduce(BinaryOperator<T> accumulator);
    该方法接受一个BinaryOperator参数，BinaryOperator是一个@FunctionalInterface,需要实现方法：
    R apply(T t, U u);
    accumulator告诉reduce方法怎么去累计stream中的数据
    */
    public void reduce01(){
        List<Integer> intList = List.of(1,2,3);
        Optional<Integer> result1=intList.stream().reduce(Integer::sum);
        System.out.println(result1);
    }

    /*
    两个参数的例子
    T reduce(T identity, BinaryOperator<T> accumulator);
     */
    public void reduce02(){
        //也许在有些文章里面有人告诉你identity是reduce的初始化值,可以随便指定,如下所示
        List<Integer> intList = List.of(1,2,3);
        Integer result2=intList.stream().reduce(100, Integer::sum);
        System.out.println(result2); // 106
        // 如果我们将stream改成parallelStream
        Integer result3=intList.parallelStream().reduce(100, Integer::sum);
        System.out.println(result3); // 306
        /*
        因为在并行计算的时候,每个线程的初始累加值都是100,最后3个线程加出来的结果就是306
        并行计算和非并行计算的结果居然不一样,这肯定不是JDK的问题,我们再看一下JDK中对identity的说明:
        identity必须是accumulator函数的一个identity,也就是说必须满足:
        对于所有的t,都必须满足 accumulator.apply(identity, t) == t
        所以这里我们传入100是不对的，因为sum（100+1）！= 1
        这里sum方法的identity只能是0
        如果我们用0作为identity,则stream和parallelStream计算出的结果是一样的。这就是identity的真正意图
         */
        Integer result4=intList.stream().reduce(0, Integer::sum);
        System.out.println(result4); // 6
        Integer result5=intList.parallelStream().reduce(0, Integer::sum);
        System.out.println(result5); // 6
    }

    /*
    三个参数的方法:
    <U> U reduce(U identity,
                 BiFunction<U, ? super T, U> accumulator,
                 BinaryOperator<U> combiner);
     和前面的方法不同的是,多了一个combiner,这个combiner用来合并多线程计算的结果
        同样的,identity需要满足combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)
     大家可能注意到了为什么accumulator的类型是BiFunction而combiner的类型是BinaryOperator?
     public interface BinaryOperator<T> extends BiFunction<T,T,T>
     BinaryOperator是BiFunction的子接口。BiFunction中定义了要实现的apply方法。
     其实reduce底层方法的实现只用到了apply方法，并没有用到接口中其他的方法，所以我猜测这里的不同只是为了简单的区分
     */
    // 对于所有的u,必须满足combiner.apply(identity, u) == u
    public void reduce03(){
        List<Integer> intList = List.of(1,2,3,4,5);
        Integer result1=intList.stream().reduce(100, Integer::sum, Integer::sum);
        System.out.println(result1);

        Integer result2=intList.parallelStream().reduce(100, Integer::sum, Integer::sum);
        System.out.println(result2);

        Integer result3=intList.stream().reduce(0, Integer::sum, Integer::sum);
        System.out.println(result3);

        Integer result4=intList.parallelStream().reduce(0, Integer::sum, Integer::sum);
        System.out.println(result4);
    }

    public void stream01(){
        List<Long> list1 = List.of(1l,2l,3l,4l,5l,6l);
        List<Long> list2 =List.of(5l,6l,7l,8l,9l);
        // 交集
        List<Long> intersection = list1.stream().filter(item -> list2.contains(item)).collect(Collectors.toList());
        System.out.println("---交集 intersection---");
        intersection.parallelStream().forEach(System.out :: println);

        // 差集 (list1 - list2)
        List<Long> reduce1 = list1.stream().filter(item -> !list2.contains(item)).collect(Collectors.toList());
        System.out.println("---差集 reduce1 (list1 - list2)---");
        reduce1.parallelStream().forEach(System.out :: println);

        // 差集 (list2 - list1)
        List<Long> reduce2 = list2.stream().filter(item -> !list1.contains(item)).collect(Collectors.toList());
        System.out.println("---差集 reduce2 (list2 - list1)---");
        reduce2.parallelStream().forEach(System.out :: println);

        // 并集
        List<Long> listAll = list1.parallelStream().collect(Collectors.toList());
        List<Long> listAll2 = list2.parallelStream().collect(Collectors.toList());
        listAll.addAll(listAll2);
        System.out.println("---并集 listAll---");
        listAll.parallelStream().forEachOrdered(System.out :: println);

        // 去重并集
        List<Long> listAllDistinct = listAll.stream().distinct().collect(Collectors.toList());
        System.out.println("---得到去重并集 listAllDistinct---");
        listAllDistinct.parallelStream().forEachOrdered(System.out :: println);

        System.out.println("---原来的List1---");
        list1.parallelStream().forEachOrdered(System.out :: println);
        System.out.println("---原来的List2---");
        list2.parallelStream().forEachOrdered(System.out :: println);
    }
}
